package slicegen;

import java.awt.geom.Point2D;

import javax.vecmath.Vector2d;

public class Ray implements Cloneable
{
    public Ray(Line line)
    {
        origin = line.getP1();

        direction = new Vector2d(line.getX2() - line.getX1(), line.getY2()
                        - line.getY1());

        originalLength = direction.length();        
        normalize();
    }

    public Ray(Point2D p1, Point2D p2)
    {
        origin = p1;

        direction = new Vector2d(p2.getX() - p1.getX(), p2.getY() - p1.getY());

        originalLength = direction.length(); 
        normalize();
    }

    public Ray(Point2D origin, Vector2d direction)
    {
        this.origin = origin;
        this.direction = direction;
        normalize();
    }

    @Override
    public String toString()
    {
        return "Origin: (" + origin.getX() + ", " + origin.getY()
                        + ") Direction: <" + direction.x + ", " + direction.y
                        + ">";
    }

    @Override
    public Object clone()
    {
        Ray clone = new Ray((Point2D) origin.clone(), (Vector2d) direction
                        .clone());

        return clone;
    }

    public Ray determineBisector(Ray r)
    {
        Vector2d bisector = new Vector2d();

        if (!r.origin.equals(origin))
        {
            System.err.println("Rays do not have the same origin:" + origin
                            + " " + r.origin);

            return null;
        }

        bisector.add(direction, r.direction);

        // System.out.println(getLine().relativeCCW(bisector.x, bisector.y));
        // if (getLine().relativeCCW(bisector.x, bisector.y) == -1)
        // bisector.negate();

        return new Ray(origin, bisector);
    }

    public void reflect(Ray r)
    {
        // Rr = Ri - 2 N (Ri . N)

        normalize();

        // Clone so we don't clobber the original bisector
        Ray normal = (Ray) r.clone();
        normal.normalize();

        normal.direction.scale(2.0 * direction.dot(normal.direction));

        direction.sub(normal.direction);
    }

    public Line getLine()
    {
        // direction.scale(originalLength);

        return new Line(origin, new Point2D.Double(origin.getX() + direction.x,
                        origin.getY() + direction.y));
    }

    public void normalize()
    {
        direction.normalize();
    }

    public void flip()
    {
        direction.negate();
    }

    public void perpendicular()
    {
        direction.set(direction.y, -direction.x);
    }

    public void setRayLength(double length)
    {
        direction.normalize();
        direction.scale(length);
    }

    public double getRayLength()
    {
        return direction.length();
    }

    public Point2D getEndpoint()
    {
        return new Point2D.Double(origin.getX() + direction.x, origin.getY()
                        + direction.y);
    }

    public Point2D getOrigin()
    {
        return origin;
    }

    public void setOrigin(Point2D origin)
    {
        this.origin = origin;
    }

    public Vector2d getDirection()
    {
        return direction;
    }

    public Point2D getRayAt(double t)
    {
        direction.normalize();

        return new Point2D.Double(origin.getX() + t * direction.x, origin
                        .getY()
                        + t * direction.y);
    }

    public Point2D intersects(Ray q)
    {
        // Normalize to make sure the parameters are valid
        normalize();
        q.normalize();

        // from
        // http://geometryalgorithms.com/Archive/algorithm_0104/algorithm_0104B.htm#Line%20Intersections
        //this ray is p

        //From q0 to p0
        Vector2d w = new Vector2d(origin.getX() - q.origin.getX(), origin
                        .getY()
                        - q.origin.getY());

        Vector2d uperp = new Vector2d(-direction.y, direction.x);

        double tI = uperp.dot(w) / uperp.dot(q.direction);

        //zero if parallel
        if (uperp.dot(q.direction) == 0 || Double.isNaN(tI))
            return null;
        else
            return q.getRayAt(tI);
    }

    protected Point2D origin;

    protected Vector2d direction;

    protected double originalLength;
}
